﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using System.Text;

using UnitUsed = System.Collections.Generic.Queue<MessageCacheUnit>;
using UnitValid = System.Collections.Generic.Stack<MessageCacheUnit>;

class MessageCacheUnit
{
    #region property
    //session identifier
    private int    m_sessionId;
    /// <summary>
    /// session identifier
    /// </summary>
    public int SessionId
    {
        get
        {
            return m_sessionId;
        }
        set
        {
            m_sessionId = value;
        }
    }
    //message's bytes length
    private uint   m_msgBytes;
    /// <summary>
    /// message's bytes length
    /// </summary>
    public uint MsgBytes
    {
        get
        {
            return m_msgBytes;
        }
        set
        {
            m_msgBytes = value;
        }
    }
    //message identifier
    private uint   m_msgId;
    /// <summary>
    /// message identifier
    /// </summary>
    public uint MsgId
    {
        get
        {
            return m_msgId;
        }
        set
        {
            m_msgId = value;
        }
    }
    //message buffer
    private byte[] m_msgBuffer;
    /// <summary>
    /// message buffer
    /// </summary>
    public byte[] Buffer
    {
        get
        {
            return m_msgBuffer;
        }
        private set
        {
            m_msgBuffer = value;
        }
    }
    #endregion

    #region constructor
    /// <summary>
    /// default constructor
    /// </summary>
    /// <param name="bytes_length">buffer length</param>
    public MessageCacheUnit(int bytes_length)
    {
        m_sessionId = -1;
        m_msgBytes = 0;
        m_msgId = 0;
        m_msgBuffer = new byte[bytes_length];
    }
    /// <summary>
    /// copy constructor
    /// </summary>
    /// <param name="bytes_length">buffer length</param>
    /// <param name="rhs">another unit</param>
    public MessageCacheUnit(int bytes_length, MessageCacheUnit rhs)
    {
        m_msgBuffer = new byte[bytes_length];
        fill(rhs);
    }
    #endregion

    #region external call
    /// <summary>
    /// fill this unit
    /// </summary>
    /// <param name="session_id">session identifier</param>
    /// <param name="msg_id">message identifier</param>
    /// <param name="msg_bytes">bytes length of the buffer</param>
    /// <param name="msg_buffer">point to buffer</param>
    public void fill(int session_id, uint msg_id, uint msg_bytes, IntPtr msg_buffer)
    {
        SessionId = session_id;
        MsgId = msg_id;
        MsgBytes = msg_bytes;
        if (msg_bytes != 0)
            Marshal.Copy(msg_buffer, m_msgBuffer, 0, (int)msg_bytes);
    }
    /// <summary>
    /// fill this unit from another
    /// </summary>
    /// <param name="rhs">another unit</param>
    public void fill(MessageCacheUnit rhs)
    {
        SessionId = rhs.SessionId;
        MsgId = rhs.MsgId;
        MsgBytes = rhs.MsgBytes;
        if (rhs.MsgBytes != 0)
            rhs.m_msgBuffer.CopyTo(m_msgBuffer, 0);
    }
    /// <summary>
    /// disacard this unit
    /// </summary>
    public void discard()
    {
        SessionId = -1;
        MsgBytes = 0;
        MsgId = 0;
    }
    #endregion
}

class MessageCache
{
    #region private variable
    //buffer size
    private int m_bufferSize;
    //all units
    private UnitValid m_stackUnits = new UnitValid();
    //units in uesed
    private UnitUsed m_queueUse = new UnitUsed();
    //lock object
    private Object m_lock = new Object();
    #endregion

    #region private function
    /// <summary>
    /// prepare allocate
    /// </summary>
    /// <param name="units_num">initialize size</param>
    private void pre_allocate(int units_num)
    {
        if (units_num > 0)
        {
            for (int i = 0; i < units_num; ++i)
            {
                m_stackUnits.Push(new MessageCacheUnit(m_bufferSize));
            }
        }
    }
    #endregion

    #region constructor
    /// <summary>
    /// constructor
    /// </summary>
    /// <param name="buffer_length">bytes length per buffer</param>
    /// <param name="units_num">initialize size</param>
    public MessageCache(int buffer_length, int units_num)
    {
        m_bufferSize = buffer_length;
        pre_allocate(units_num);
    }
    #endregion

    #region external call
    /// <summary>
    /// store message
    /// </summary>
    /// <param name="session_id">session identifier</param>
    /// <param name="msg_id">message identifier</param>
    /// <param name="msg_bytes">bytes lengh of the buffer</param>
    /// <param name="pMsgBuffer">point to buffer</param>
    public void write(int session_id, uint msg_id, uint msg_bytes, IntPtr pMsgBuffer)
    {
        lock (m_lock)
        {
            if (m_stackUnits.Count > 0)
            {
                MessageCacheUnit unit = m_stackUnits.Pop();
                unit.fill(session_id, msg_id, msg_bytes, pMsgBuffer);
                m_queueUse.Enqueue(unit);
            }
            else
            {
                MessageCacheUnit unit = new MessageCacheUnit(m_bufferSize);
                unit.fill(session_id, msg_id, msg_bytes, pMsgBuffer);
                m_queueUse.Enqueue(unit);
            }
        }
    }
    /// <summary>
    /// get the first unit
    /// </summary>
    /// <param name="unit">the unit be filled</param>
    public void read(ref MessageCacheUnit unit)
    {
        lock (m_lock)
        {
            if (m_queueUse.Count > 0)
            {
                MessageCacheUnit cacheUnit = m_queueUse.Dequeue();
                unit.fill(cacheUnit);
                cacheUnit.discard();
                m_stackUnits.Push(cacheUnit);
            }
        }
    }
    /// <summary>
    /// get the valid units count
    /// </summary>
    /// <returns></returns>
    public int size()
    {
        lock (m_lock)
        {
            return m_stackUnits.Count;
        }
    }
    #endregion
}